/*
 * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.wink.cachedemo.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.listener.PatternTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.data.redis.listener.adapter.MessageListenerAdapter;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.time.Duration;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @author lengleng
 * @date 2019/2/1 Redis 配置类
 */
@EnableCaching // 开启缓存
@Configuration
@RequiredArgsConstructor
@AutoConfigureBefore(RedisAutoConfiguration.class)
public class RedisTemplateConfiguration {

    private final RedisConnectionFactory factory;

    /**
     * 使用Jackson2JsonRedisSerialize 替换默认序列化
     */
    @Bean
    public RedisTemplate<String, Object> redisTemplate() {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        /*设置value的序列化规则和 key的序列化规则*/
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,
                ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
        // 另一种写法
        // objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.setConnectionFactory(factory);
        return redisTemplate;
    }


    /**
     * 缓存管理器
     */
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
        return RedisCacheManager.builder(redisConnectionFactory)
                //默认的缓存配置(没有配置键的key均使用此配置)
                .cacheDefaults(getRedisCacheConfiguration(0))
                //设置不同key过期策略
                .withInitialCacheConfigurations(getRedisCacheConfigurationMap())
                .transactionAware() //在spring事务正常提交时才缓存数据
                .build();
    }

    /**
     * 使用Jackson2JsonRedisSerialize 替换默认缓存序列化，不设置会使用默认JDK序列化，二进制非json
     * 当设置为 0 即 Duration.ZERO 时表示键无过期时间,其也是默认配置
     */
    private RedisCacheConfiguration getRedisCacheConfiguration(Integer seconds) {
        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,
                ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig();
        redisCacheConfiguration = redisCacheConfiguration.serializeValuesWith(
                RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
                .entryTtl(Duration.ofSeconds(seconds));
        return redisCacheConfiguration;
    }

    private Map<String, RedisCacheConfiguration> getRedisCacheConfigurationMap() {
        Map<String, RedisCacheConfiguration> redisCacheConfigurationMap = new HashMap<>(2);
        //过期时间配置
        redisCacheConfigurationMap.put("expireKey", this.getRedisCacheConfiguration( 6000));
        redisCacheConfigurationMap.put("key", this.getRedisCacheConfiguration(0));
        return redisCacheConfigurationMap;
    }

    /**
     * 自定义key的生成规则
     */
    @Bean(name = "serviceKeyGenerator")
    public KeyGenerator serviceKeyGenerator() {
        return (target, method, params) ->
                //执行方法所在的类:方法名
                target.getClass().getSimpleName() + ":" + method.getName() +
                        Stream.of(params)
                                .map(String::valueOf)
                                .collect(Collectors.joining("_"));
    }


    /**
     * 配置主题订阅
     *
     * @param connectionFactory
     * @param listenerAdapter
     * @return
     */
    @Bean
    public RedisMessageListenerContainer listenerContainer(RedisConnectionFactory connectionFactory, MessageListenerAdapter listenerAdapter) {
        RedisMessageListenerContainer listenerContainer = new RedisMessageListenerContainer();
        listenerContainer.setConnectionFactory(connectionFactory);
        List<PatternTopic> topics = Arrays.asList(
                // 要监听的主题，字符串即可
                new PatternTopic("abc"),
                new PatternTopic("efg"),
                new PatternTopic("ccccc")
        );
        listenerContainer.addMessageListener(listenerAdapter, topics);
        //指定消费线程池，因默认使用的SimpleAsyncTaskExecutor 即每次创建新thread
        listenerContainer.setTaskExecutor(springRedisTaskExecutor());
        return listenerContainer;
    }


    /**
     * 用于redis 发布订阅，防止每次监听执行任务创建一个线程，导致创建线程过多
     *
     * @return
     */
    @Bean
    public ThreadPoolTaskExecutor springRedisTaskExecutor(){
    	ThreadPoolTaskExecutor springSessionRedisTaskExecutor = new ThreadPoolTaskExecutor();
    	springSessionRedisTaskExecutor.setCorePoolSize(8);
    	springSessionRedisTaskExecutor.setMaxPoolSize(16);
    	springSessionRedisTaskExecutor.setKeepAliveSeconds(10);
    	springSessionRedisTaskExecutor.setQueueCapacity(1000);
    	springSessionRedisTaskExecutor.setThreadNamePrefix("Redis-executor-thread:");
    	return springSessionRedisTaskExecutor;
    }

}
